/* Copyright 2010 by Matt Van Der Westhuizen */

package org.chaoticengine.ast.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.chaoticengine.ast.pattern.Model;
import org.jgrapht.EdgeFactory;
import org.jgrapht.alg.BellmanFordShortestPath;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleGraph;
import org.newdawn.slick.geom.Vector2f;

/**
 * Model of the game world map, which can contain towns, airships and different
 * terrain types.
 *
 * @author Matt Van Der Westhuizen <mattpwest@gmail.com>
 */
public class WorldMap extends Model implements HasTurn {
    protected int minX = 0;
    protected int maxX = 0;
    protected int minY = 0;
    protected int maxY = 0;
    protected int width = 0;
    protected int height = 0;
    protected SimpleGraph<Location, LocationEdge> map = null;
    protected HashMap<Vector2f, Location> locations = null;

    public WorldMap() {
        super();

        map = new SimpleGraph<Location, LocationEdge>(new EdgeFactory<Location, LocationEdge>() {
            public LocationEdge createEdge(Location sourceVertex, Location targetVertex) {
                LocationEdge result = new LocationEdge(sourceVertex, targetVertex);
                return(result);
            }

        });
        locations = new HashMap<Vector2f, Location>();
    }

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

    public void takeTurn(float hours) {
        Iterator<AirShip> shipIt = AirShipFactory.getInstance().getShips().iterator();
        while (shipIt.hasNext()) {
            shipIt.next().takeTurn(hours);
        }

        refreshObservers();
    }

    public void addLocation(Location loc) {
        if (loc == null) {
            return;
        }
        
        if (!locations.containsKey(loc.getPosition())) {
            map.addVertex(loc);
            locations.put(loc.getPosition(), loc);

            Vector2f p = loc.getPosition();
            if (p.getX() < minX) {
                minX = (int) p.getX();
            } else if (p.getX() > maxX) {
                maxX = (int) p.getX();
            }

            if (p.getY() < minY) {
                minY = (int) p.getY();
            } else if (p.getY() > maxY) {
                maxY = (int) p.getY();
            }

            width = maxX - minX + 1;
            height = maxY - minY + 1;
        }
    }

    public Location getLocation(int x, int y) {
        Vector2f p = new Vector2f(x, y);
        return(locations.get(p));
    }

    public void moveLocation(Location loc, int x, int y) {
        Vector2f p = loc.getPosition();
        locations.remove(p);

        // Update position
        loc.setPosition(x, y);
        p = loc.getPosition();
        locations.put(p, loc);

        // Update map dimensions
        if (p.getX() < minX) {
            minX = (int) p.getX();
        } else if (p.getX() > maxX) {
            maxX = (int) p.getX();
        }

        if (p.getY() < minY) {
            minY = (int) p.getY();
        } else if (p.getY() > maxY) {
            maxY = (int) p.getY();
        }

        width = maxX - minX + 1;
        height = maxY - minY + 1;

        refreshObservers();
    }

    public Iterator<Location> getLocations() {
        return(locations.values().iterator());
    }

    public void addPath(Location loc1, Location loc2) {
        map.addEdge(loc1, loc2);
    }

    public List<ShipLocation> findPathBetween(Location src, Location dest) {
        BellmanFordShortestPath<Location, LocationEdge> sp =
                new BellmanFordShortestPath<Location, LocationEdge>(map, src) {
            @Override
            public double getCost(Location dest) {
                Altitude sAlt = this.startVertex.getAltitude();
                Vector2f sPos = this.startVertex.getPosition();
                Altitude dAlt = dest.getAltitude();
                Vector2f dPos = dest.getPosition();

                Float dist = sPos.distance(dPos);
                Float altMod = (dAlt.ordinal() + 1) / (float) (sAlt.ordinal() + 1);
                return(dist * altMod);
            }
        };

        List<LocationEdge> edges = sp.getPathEdgeList(dest);
        List<ShipLocation> path = new ArrayList<ShipLocation>();

        path.add(src);
        Iterator<LocationEdge> edgeIt = edges.iterator();
        while (edgeIt.hasNext()) {
            LocationEdge edge = edgeIt.next();
            if (edge.getSource() == src) {
                path.add(edge);
                path.add(edge.dst);
                src = edge.dst;
            } else {
                path.add(edge);
                path.add(edge.src);
                src = edge.src;
            }
        }

        return(path);
    }

    public Iterator<Location> getNeighbours(Location loc) {
        ArrayList<Location> result = new ArrayList<Location>();
        Iterator<LocationEdge> eIt = map.edgesOf(loc).iterator();

        while (eIt.hasNext()) {
            LocationEdge edge = eIt.next();

            if (edge.getSource().equals(loc)) {
                result.add((Location) edge.getTarget());
            } else {
                result.add((Location) edge.getSource());
            }
        }

        return(result.iterator());
    }

    public Iterator<LocationEdge> getPaths(Location loc) {
        return(map.edgesOf(loc).iterator());
    }

    public class LocationEdge extends DefaultEdge implements ShipLocation {
        protected Location src = null;
        protected Location dst = null;
        protected List<AirShip> ships = null;

        public LocationEdge(Location src, Location dest) {
            this.src = src;
            this.dst = dest;
            this.ships = new ArrayList<AirShip>();
        }

        @Override
        public Object getSource() {
            return(src);
        }

        @Override
        public Object getTarget() {
            return(dst);
        }

        @Override
        public String toString() {
            return super.toString();
        }

        public void addShip(AirShip ship) {
            this.ships.add(ship);
        }

        public void clearShips() {
            this.ships.clear();
        }

        public List<AirShip> getShips() {
            return(this.ships);
        }

        public void setShips(List<AirShip> ships) {
            this.ships = ships;
        }

        public void removeShip(AirShip ship) {
            this.ships.remove(ship);
        }
    }
}
